/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.rainbow.xml;

import java.io.File;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.rainbow.file.FileCommonHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

/**
 *
 * @author hironaga
 */
public final class XmlCommonHandler extends FileCommonHandler{

    private static XmlCommonHandler instance = new XmlCommonHandler();

    static XmlCommonHandler getInstance(){
        return instance;
    }

    protected XmlCommonHandler() {}
    
    final String DEFAULT_VERSION = "1.0";

    final String DEFAULT_INDENT = "    ";

    //protected static String LINE_BREAK = System.getProperty("line.separator");
    final String LINE_BREAK = "\n";


    //Document取得
    Document getDocument(File file)
                                                       throws IOException{

        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        domFactory.setNamespaceAware(true);
        DocumentBuilder builder = null;
        Document doc = null;
        try {
            builder = domFactory.newDocumentBuilder();
            doc = builder.parse(file.getAbsolutePath());
        } catch (ParserConfigurationException ex) {
            throw new ParserConfigurationRuntimeException(ex);
        } catch (SAXException ex) {
            throw new SAXRuntimeException(ex);
        }

        return doc;
    }

    boolean isNewDocument(File file) throws IOException{

        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        domFactory.setNamespaceAware(true);
        DocumentBuilder builder = null;
        Document doc = null;
        try {
            builder = domFactory.newDocumentBuilder();
            doc = builder.parse(file.getAbsolutePath());
        } catch (ParserConfigurationException ex) {
            return true;
        } catch (SAXException ex) {
            return true;
        }

        if(doc != null && doc.hasChildNodes()){
            return false;
        }
        else{
            return true;
        }
        
    }

    //新規Document取得
    Document getNewDocument() throws IOException{

        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        domFactory.setNamespaceAware(true);
        DocumentBuilder builder;
        Document doc;
        try {
            builder = domFactory.newDocumentBuilder();
            doc = builder.newDocument();
        } catch (ParserConfigurationException ex) {
            throw new ParserConfigurationRuntimeException(ex);
        }

        return doc;
    }

    //NodeList取得
    NodeList getNodeList(Document doc, String xPath)
                                                        throws IOException{

        validateXpathForRead(xPath);

        //NodeList取得
        XPathFactory xfactory = XPathFactory.newInstance();
        XPath xpath = xfactory.newXPath();
        XPathExpression expr = null;
        NodeList nodeList = null;
        try {
            expr = xpath.compile(xPath);
            //TODO NODEの場合、XPathConstants.NODE
            nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
        } catch (XPathExpressionException ex) {
            throw new XPathExpressionRuntimeException(ex);
        }

        return nodeList;
    }

    //Node取得
    Node getNode(Document doc, String xPath)
                                                        throws IOException{

        validateXpathForRead(xPath);

        //NodeList取得
        XPathFactory xfactory = XPathFactory.newInstance();
        XPath xpath = xfactory.newXPath();
        XPathExpression expr = null;
        Node node = null;
        try {
            expr = xpath.compile(xPath);
            //TODO NODEの場合、XPathConstants.NODE
            node = (Node) expr.evaluate(doc, XPathConstants.NODE);
        } catch (XPathExpressionException ex) {
            throw new XPathExpressionRuntimeException(ex);
        }

        return node;
    }

    String getNodeAttrValue(Node node, String attrName){

        String attrValue = null;
        NamedNodeMap attrNodes = node.getAttributes();
        if(attrNodes != null){
            Node attrNode = attrNodes.getNamedItem(attrName);
            if(attrNode != null){
                attrValue = attrNode.getNodeValue();
            }
        }
        return attrValue;
    }

    void addHeaderElement(Document doc, String version) throws IOException{

        doc.setXmlVersion(version);

    }

    private Node addRootNode(Document doc, String rootName) throws IOException{

        Node rootNode = createNode(doc, rootName);
        doc.appendChild(rootNode);
        return rootNode;

    }

    private String[] validateXpath(String xpath, int length){

        if( xpath == null || xpath.length() == 0 ){
            throw new IllegalArgumentException("Xpath is empty");
        }

        String[] elements = xpath.split("/");

        if(elements.length < length || elements[0].length() != 0){
            throw new IllegalArgumentException("Xpath is bad format");
        }

        return elements;
    }

    String[] validateXpathForRead(String xpath){

        return validateXpath(xpath, 2);

    }

    String[] validateXpathForAppend(String xpath){

        return validateXpath(xpath, 3);

    }

    Node createParerntElement(Document doc, String xpath) throws IOException{

//        if( xpath == null || xpath.length() == 0 ){
//            throw new IllegalArgumentException("Xpath is empty");
//        }

        String[] elements = validateXpathForAppend(xpath);

//        if(elements.length < 2 || elements[0].length() != 0){
//            throw new IllegalArgumentException("Xpath is bad format");
//        }

        //Add root
        Node rootNode = addRootNode(doc, elements[1]);

        //Add element 2 root
        Node parentNode = rootNode;

        for(int i=2; i<elements.length; i++){
//            //make br
//            Text br = createText(doc, LINE_BREAK);
//            parentNode.appendChild(br);
//            //make indent
//            Text indent = createText(doc, DEFAULT_INDENT);
//            for(int j=0; j<i-1; j++){
//                parentNode.appendChild(indent);
//            }
            Node newElement = createNode(doc, elements[i]);
            parentNode.appendChild(newElement);
            parentNode = newElement;
        }

        return parentNode;
    }

    Node getLastElement(Document doc, String xpath, int index)
                                                            throws IOException{

        //validateXpath(xpath);     //already checked

        NodeList nodeList = getNodeList(doc, xpath);
        if( nodeList != null && nodeList.getLength() > 0 && index < nodeList.getLength()){
            return nodeList.item( index );
        }
        else{
            return null;
        }
    }

    Node getLastElement(Document doc, String xpath, String elementName, int index)
                                                            throws IOException{

        //validateXpath(xpath);     //already checked

        String innerXpath = xpath + "/" + elementName;
        NodeList nodeList = getNodeList(doc, innerXpath);
        if( nodeList != null && nodeList.getLength() > 0 && index < nodeList.getLength()){
            return nodeList.item( index );
        }
        else{
            return null;
        }
    }

    Node getParentElement(Document doc, String xpath)
                                                            throws IOException{

        String newXpath = buildXpathToGetParentElement(xpath);

        return getNode(doc, newXpath);
    }

    Node appendElement(Document doc, Node parentNode, String elementName)
                                                             throws IOException{

//        //make br
//        Text br = createText(doc, LINE_BREAK);
//        parentNode.appendChild(br);
//        //make indent
//        Text indent = createText(doc, DEFAULT_INDENT);

        Node newElement = createNode(doc, elementName);
        parentNode.appendChild(newElement);
        return newElement;
    }

    Node apendElement(Document doc, Node parentNode, Node childNode)
                                                             throws IOException{

//        //make br
//        Text br = createText(doc, LINE_BREAK);
//        parentNode.appendChild(br);
//        //make indent
//        Text indent = createText(doc, DEFAULT_INDENT);

        parentNode.appendChild(childNode);
        return childNode;
    }

    Node createNode(Document doc, String nodeName){
        return doc.createElement(nodeName);
    }

    Text createText(Document doc, String textValue){
        return doc.createTextNode(textValue);
    }

    void setNodeAttr(Node node, String attrName, String attrValue){

        Element element = (Element)node;
        element.setAttribute(attrName, attrValue);
    }

    void apendTextNode(Document doc, Node node, String textValue){

        Text newText = createText(doc, textValue);
        node.appendChild(newText);

    }

    //TODO
//    void deleteTextNode(Document doc, Node node){
//
//        Text newText = createText(doc, textValue);
//        node.appendChild(newText);
//
//    }


    void exchangeNodeTextValue(Node node, String textValue){
        //TODO
    }

    Node copyNode(Node node){
        return node.cloneNode(true);
    }

    //(TYPE)ELEMENT_NODE取得
    Node getElementNode(Node node){

        while(node != null && node.getNodeType() != Node.ELEMENT_NODE){
               node = node.getNextSibling();
        }
        return node;
    }

    //Get Text Node
    Node getTextNode(Node node){

        if(node != null && node.hasChildNodes()){
            Node childNode = node.getFirstChild();
            while(childNode != null && childNode.getNodeType() != Node.TEXT_NODE){
                childNode = childNode.getNextSibling();
            }
            return childNode;
        }
        else{
            return node;
        }
    }

    //Get Text Node
    String getTextNodeValue(Node textNode){

        if(!(textNode == null) && textNode.getNodeType() == Node.TEXT_NODE){
            return textNode.getNodeValue();
        }
        else{
            return null;
        }
    }

    String getLastElementNameFromXpath(String xpath){

        String[] elements = validateXpathForAppend(xpath);

        return elements[ elements.length - 1 ];

    }

    private String buildXpathToGetParentElement(String xpath){

        String[] elements = validateXpathForAppend(xpath);

        StringBuilder builder = new StringBuilder();
        for(int i=1; i<elements.length - 1; i++){
            builder.append("/");
            builder.append(elements[i]);
        }
        return builder.toString();
    }

}
